package org.infodavid.common.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * The Class PropertiesUtil.
 */
public final class PropertiesUtil {

  /** The Constant log. */
  private static final Log LOGGER = LogFactory.getLog(PropertiesUtil.class);

  /** The singleton. */
  private static WeakReference<PropertiesUtil> singleton = null;

  /**
   * returns the singleton.
   * @return <code>PropertiesUtil</code>
   */
  public static synchronized PropertiesUtil getInstance() {
    if (singleton == null || singleton.get() == null) {
      singleton = new WeakReference<PropertiesUtil>(new PropertiesUtil());
    }

    return singleton.get();
  }

  /** The is debug enabled. */
  private final boolean isDebugEnabled = LOGGER.isDebugEnabled();

  /**
   * Instantiates a new properties util.
   */
  private PropertiesUtil() {
    super();
  }

  /**
   * Gets the property value.
   * @param property the property
   * @param defaultValue the default value
   * @return the property value
   */
  public boolean getSystemPropertyValue(final String property, final boolean defaultValue) {
    String strValue = System.getProperty(property);
    boolean value;

    if (StringUtils.isEmpty(strValue)) {
      if (isDebugEnabled) {
        LOGGER.debug("Using default value for property: " + property);
      }

      value = defaultValue;
    }
    else {
      strValue = strValue.trim().toLowerCase();

      try {
        value = toBoolean(strValue);
      }
      catch (final Exception e) {
        LOGGER.warn("Error wjile retrieving value of property: " + property
            + ", using default value");

        value = defaultValue;
      }
    }

    if (isDebugEnabled) {
      LOGGER.debug("Value for property: " + property + " is " + value);
    }

    return value;
  }

  /**
   * Gets the property value.
   * @param property the property
   * @param defaultValue the default value
   * @return the property value
   */
  public long getSystemPropertyValue(final String property, final long defaultValue) {
    String strValue = System.getProperty(property);
    long value;

    if (StringUtils.isEmpty(strValue)) {
      if (isDebugEnabled) {
        LOGGER.debug("Using default value for property: " + property);
      }

      value = defaultValue;
    }
    else {
      strValue = strValue.trim();

      try {
        value = Double.valueOf(strValue).longValue();
      }
      catch (final Exception e) {
        LOGGER.warn("Error wjile retrieving value of property: " + property
            + ", using default value");

        value = defaultValue;
      }
    }

    if (isDebugEnabled) {
      LOGGER.debug("Value for property: " + property + " is " + value);
    }

    return value;
  }

  /**
   * Gets the property value.
   * @param property the property
   * @param defaultValue the default value
   * @return the property value
   */
  public String getSystemPropertyValue(final String property, final String defaultValue) {
    final String strValue = System.getProperty(property);
    String value;

    if (StringUtils.isEmpty(strValue)) {
      if (isDebugEnabled) {
        LOGGER.debug("Using default value for property: " + property);
      }

      value = defaultValue;
    }
    else {
      value = strValue.trim().toLowerCase();
    }

    if (isDebugEnabled) {
      LOGGER.debug("Value for property: " + property + " is " + value);
    }

    return value;
  }

  /**
   * Read parameter.
   * @param is the input stream
   * @param key the key
   * @return the string
   * @throws IOException Signals that an I/O exception has occurred.
   */
  public String readParameter(final InputStream is, final String key) throws IOException {
    final BufferedReader br = new BufferedReader(new InputStreamReader(is));
    final String prefix = new StringBuilder().append(key).append('=').toString();
    String value = null;
    String line;
    String[] a;

    while ((line = br.readLine()) != null && value == null) {
      if (line.charAt(0) == '#') {}
      else if (line.startsWith(prefix)) {
        a = line.split("=");

        if (a.length > 1) {
          value = a[1];
        }
      }
    }

    try {
      br.close();
    }
    catch (final Exception e) {
      LOGGER.error(this + ", " + key, e);
    }

    try {
      is.close();
    }
    catch (final Exception e) {
      LOGGER.error(this + ", " + key, e);
    }

    return value;
  }

  /**
   * Read parameters.
   * @param is the input stream
   * @param keys the keys
   * @return the string[]
   * @throws IOException Signals that an I/O exception has occurred.
   */
  public String[] readParameters(final InputStream is, final String... keys) throws IOException {
    final BufferedReader br = new BufferedReader(new InputStreamReader(is));
    final String[] values = new String[keys.length];
    int found = 0;
    int i;
    String line;
    String[] a;

    while ((line = br.readLine()) != null && found < keys.length) {
      if (line.charAt(0) == '#') {}
      else {
        a = line.split("=");
        i = search(keys, a[0]);

        if (i >= 0) {
          found++;
          values[i] = a[1];
        }
      }
    }

    try {
      br.close();
    }
    catch (final Exception e) {
      LOGGER.error(this + ", " + Arrays.toString(keys), e);
    }

    try {
      is.close();
    }
    catch (final Exception e) {
      LOGGER.error(this + ", " + Arrays.toString(keys), e);
    }

    return values;
  }

  /**
   * Read parameters.
   * @param is the input stream
   * @param separator the separator
   * @return the map< string, string[]>
   * @throws IOException Signals that an I/O exception has occurred.
   */
  public Map<String,String[]> readParameters(final InputStream is, final String separator)
      throws IOException {
    final BufferedReader br = new BufferedReader(new InputStreamReader(is));
    final Map<String,String[]> map = new HashMap<String,String[]>();
    String line;
    String[] a;

    while ((line = br.readLine()) != null) {
      if (line.charAt(0) == '#') {}
      else {
        a = line.split("=");

        if (a.length > 1) {
          map.put(a[0], a[1].split(separator));
        }
      }
    }

    try {
      br.close();
    }
    catch (final Exception e) {
      LOGGER.error(this + ", " + separator, e);
    }

    try {
      is.close();
    }
    catch (final Exception e) {
      LOGGER.error(this + ", " + separator, e);
    }

    return map;
  }

  /**
   * Search.
   * @param array the array
   * @param key the key
   * @return the position
   */
  private int search(final String[] array, final String key) {
    int index = -1;
    int i = 0;

    if (array != null) {
      final int count = array.length;

      if (key == null) {
        while (i < count && index == -1) {
          index = array[i] == null ? i : -1;
          i++;
        }
      }
      else {
        while (i < count && index == -1) {
          index = key.equals(array[i]) ? i : -1;
          i++;
        }
      }
    }

    return index;
  }

  /**
   * To boolean.
   * @param value the value
   * @return true, if successful
   */
  public boolean toBoolean(final String value) {
    return value != null
        && (value.equals("true") || value.equals("on") || value.equals("yes") || value.equals("1"));
  }

  /**
   * To boolean.
   * @param value the value
   * @return true, if successful
   */
  @SuppressWarnings("boxing")
  public Boolean toBooleanObject(final String value) {
    return value == null ? null :
        value.equals("true") || value.equals("on") || value.equals("yes") || value.equals("1");
  }

  /**
   * To number.
   * @param value the value
   * @return the number
   */
  public Number toNumber(final String value) {
    return value == null || value.trim().length() == 0 ? null : Double.valueOf(value);
  }
}
